%{
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <list>
#include <string>

#include "cpp.h"

// dont include unistd.h in windows
#ifdef WIN32
    #define YY_NO_UNISTD_H 1
#endif

#define YY_NEVER_INTERACTIVE 1

    static const char *curr_script_buffer;

    static int script_len;

    static int script_pos;

// Wrap our getc, so that lex doesn't try to do its own buffering/file IO.
#define YY_INPUT(buf,result,max_size)                                   \
    {                                                                   \
        int bytes_remain = script_len - script_pos;                     \
        if (bytes_remain > 0) {                                         \
            int bytes_copy =                                            \
                (bytes_remain > max_size) ? max_size : bytes_remain;    \
            memcpy(buf, curr_script_buffer + script_pos, bytes_copy);   \
            script_pos += bytes_copy;                                   \
            result = bytes_copy;                                        \
        }                                                               \
        else                                                            \
            result = 0;                                                 \
    }

    void lex_reset_buffer(const char *sb, int size)
    {
        curr_script_buffer = sb;
        script_len = size;
        script_pos = 0;
        yylineno   = 1;

        yyrestart(yyin);
    }

    static std::string _macro_define;

#define MACRO_INPUT(c)                              \
    do {                                            \
        c = yyinput();                              \
        if (c > 0)                                  \
            if (is_define)                          \
                _macro_define.push_back((char)c);   \
    } while (0)

#define MACRO_UNPUT(c)                                      \
    do {                                                    \
        unput(c);                                           \
        if (is_define && _macro_define.size() > 0)          \
            _macro_define.resize(_macro_define.size() - 1); \
    } while (0)
%}

%option never-interactive

integer         (0|[1-9][0-9]*)|0[0-7]+|0[xX][0-9a-fA-F]+
integer_suffix  [uU]?([lL]|[lL][lL]|[iI]64)?
ident           [_a-zA-Z][_0-9a-zA-Z]*
newline         (\r\n|\r|\n)
%%
"//".*{newline} {  }
"/*" {
    int lst = 0;
    for (;;) {
        int cur = yyinput();
        if (lst == '*' && cur == '/')
            break;
        else if (cur <= 0)
            break;
        lst = cur;
    }
}
{integer}{integer_suffix}   { return tkINT; }
(0|[1-9][0-9]*)?\.[0-9]+([eE][-+]?[0-9]+)?[fFlL]? { return tkFLOAT; }
\" {
    int lst = 0;
    for (;;) {
        int cur = yyinput();
        if (lst == '\\') {
            lst = 0;
        }
        else if (cur == '"')
            break;
        else if (cur <= 0)
            break;
        else
            lst = cur;
    }
    return tkSTRING;
}
' {
    int lst = 0;
    for (;;) {
        int cur = yyinput();
        if (lst == '\\') {
            lst = 0;
        }
        else if (cur == '\'')
            break;
        else if (cur <= 0)
            break;
        else
            lst = cur;
    }
    return tkCHAR;
}
"try" { return rwTRY; }
"catch" { return rwCATCH; }
"finally" { return rwFINALLY; }
"if" { return rwIF; }
"else" { return rwELSE; }
"do" { return rwDO; }
"while" { return rwWHILE; }
"for" { return rwFOR; }
"switch" { return rwSWITCH; }
"case" { return rwCASE; }
"default" { return rwDEFAULT;}
"enum" { return rwENUM; }
"union" { return rwUNION; }
"struct" { return rwSTRUCT; }
"class" { return rwCLASS; }
"template" { return rwTEMPLATE; }
"typename" { return rwTYPENAME; }
"typedef" { return rwTYPEDEF; }
"namespace" { return rwNAMESPACE; }
"sizeof" { return rwSIZEOF; }
"new" { return rwNEW; }
"delete" { return rwDELETE; }
"continue" { return rwCONTINUE; }
"break" { return rwBREAK; }
"asm"|"__asm__" { return rwASM; }
"attribute"|"__attribute__" { return rwATTRIBUTE; }
"mutable" { return rwMUTABLE; }
"const_cast" {return rwCONST_CAST; }
"static_cast" { return rwSTATIC_CAST; }
"dynamic_cast" { return rwDYNAMIC_CAST; }
"reinterpret_cast" { return rwREINTERPRET_CAST; }
"inline" { return rwINLINE; }
"return" { return rwRETURN; }
"goto" { return rwGOTO; }
"throw" { return rwTHROW; }
"volatile"|"__volatile"|"__volatile__" { return rwVOLATILE; }
"static" { return rwSTATIC; }
"extern" { return rwEXTERN; }
"using" { return rwUSING; }
"public" { return rwPUBLIC; }
"protected" { return rwPROTECTED; }
"private" { return rwPRIVATE; }
"register" { return rwREGISTER; }
"this" { return rwTHIS; }
"friend" { return rwFRIEND; }
"explicit" { return rwEXPLICIT; }
"typeid" { return rwTYPEID; }
"operator" { return rwOPERATOR; }
"const" { return rwCONST; }
"virtual" {  }
{ident} { return tkIDENT; }
"::" { return tkCC; }
":" { return tkCOLON; }
"(" { return tkLEFTP; }
")" { return tkRIGHTP; }
"~" { return tkWAVE; }
"." { return tkDOT; }
"," { return tkCOMMA; }
"{" { return tkLEFTB; }
"}" { return tkRIGHTB; }
";" { return tkTERM; }
^[ \t]*# {
    // solved the problem in the source like
    // #define macro value /* multi-line
    //                        comment */

    // check the type of pre-process instruction
    // we just handle #define
    std::string ins;
    int         cur, lst;
    bool        is_define = false;

    for (;;) {
        cur = yyinput();
        if (cur != ' ' && cur != '\t')
            break;
        else if (cur <= 0)
            break;
    }

    if (cur == 'd') {
        ins.push_back('d');

        for (;;) {
            cur = yyinput();
            if (cur >= 'a' && cur <= 'z')
                ins.push_back((char)cur);
            else
                break;
        }

        if (ins == "define") {
            _macro_define = "#define ";
            is_define = true;
        }
    }

    // ignore until new line
    lst = 0;
    bool done = false;
    while (!done) {
        MACRO_INPUT(cur);
        switch (cur) {
        case '\r':
            MACRO_INPUT(cur);
            if (cur > 0 && cur != '\n') {
                MACRO_UNPUT(cur);
                cur = '\r';
            }
            if (lst != '\\')
                done = true;
            break;

        case '\n':
            if (lst != '\\')
                done = true;
            break;

        case '/':
            MACRO_INPUT(cur);
            if (cur == '*') {
                // begin a comment
                for (;;) {
                    MACRO_INPUT(cur);
                    if (cur == '*') {
                        MACRO_INPUT(cur);
                        if (cur == '/')
                            break;
                        else {
                            if (cur > 0)
                                MACRO_UNPUT(cur);
                            cur = '*';
                        }
                    }
                    else if (cur <= 0)
                        break;
                }
            }
            else {
                if (cur > 0)
                    MACRO_UNPUT(cur);
                cur = '*';
            }
            break;

        case '"':
            for (;;) {
                MACRO_INPUT(cur);
                if (cur == '\\') {
                    MACRO_INPUT(cur);
                }
                else if (cur <= 0 || cur == '"')
                    break;
            }
            break;
        }

        if (cur <= 0)
            break;
        else if (lst == '\\')
            lst = 0;
        else
            lst = cur;
    }

    if (is_define)
        return tkMACRO_DEFINE;
}
"==" |
"!=" |
"<=" |
">=" |
"||" |
"&&" |
"<<" |
">>" |
"++" |
"--" |
"->" |
"+=" |
"-=" |
"*=" |
"/=" |
"%=" |
"&=" |
"^=" |
"|=" |
"<<=" |
">>=" |
"!" |
"+" |
"-" |
"/" |
"%" |
"^" |
"|" |
"?" { return tkOP; }
"<" { return tkLEFTA; }
">" { return tkRIGHTA; }
"&" { return tkREF; }
"*" { return tkPTR; }
"=" { return tkASS; }
"[" { return tkLEFTS; }
"]" { return tkRIGHTS; }
" " |
\\ |
\f |
\t |
\r |
\n {}
. { fprintf(stderr, "unknow char `%c' hex 0x%x\n", *yytext, (unsigned char) *yytext); return -1; }
%%

int yywrap() { return 1; }

typedef std::list<TokenDesc> TokenList;

static TokenList unput_list;

int lex_get_token(std::string& text, int *lineno)
{
    int r;
    if (unput_list.size() > 0) {
        TokenDesc& back = unput_list.back();
        text = back.text;
        *lineno = back.lineno;
        r = back.token;
        unput_list.pop_back();
    }
    else {
        r = yylex();
        if (r == tkMACRO_DEFINE)
            text = _macro_define;
        else
            text = yytext;
        *lineno = yylineno;
    }

    return r;
}

void lex_unput_token(int token, const char *text, int lineno)
{
    unput_list.push_back(TokenDesc(token, text, lineno));
}

#if 0
int main()
{
    int done = 0;
    while (!done) {
        int type = yylex();
        switch (type) {
        case tkIDENT:
            printf("ident `%s'\n", yytext);
            break;
        case -1:
            printf("stop at lineno %d\n", yylineno);
            return 1;
            break;
        case 0:
            done = 1;
            break;
        case tkINT:
            printf("tkINT\n");
            break;
        case tkFLOAT:
            printf("tkFLOAT\n");
            break;
        case tkSTRING:
            printf("tkSTRING: `%s'\n", yytext);
            break;
        case tkCHAR:
            printf("tkCHAR\n");
            break;
        case rwTRY:
            printf("rwTRY\n");
            break;
        case rwCATCH:
            printf("rwCATCH\n");
            break;
        case rwFINALLY:
            printf("rwFINALLY\n");
            break;
        case rwIF:
            printf("rwIF\n");
            break;
        case rwELSE:
            printf("rwELSE\n");
            break;
        case rwDO:
            printf("rwDO\n");
            break;
        case rwWHILE:
            printf("rwWHILE\n");
            break;
        case rwFOR:
            printf("rwFOR\n");
            break;
        case rwSWITCH:
            printf("rwSWITCH\n");
            break;
        case rwCASE:
            printf("rwCASE\n");
            break;
        case rwDEFAULT:
            printf("rwDEFAULT\n");
            break;
        case rwENUM:
            printf("rwENUM\n");
            break;
        case rwUNION:
            printf("rwUNION\n");
            break;
        case rwSTRUCT:
            printf("rwSTRUCT\n");
            break;
        case rwCLASS:
            printf("rwCLASS\n");
            break;
        case rwTEMPLATE:
            printf("rwTEMPLATE\n");
            break;
        case rwTYPENAME:
            printf("rwTYPENAME\n");
            break;
        case rwTYPEDEF:
            printf("rwTYPEDEF\n");
            break;
        case rwNAMESPACE:
            printf("rwNAMESPACE\n");
            break;
        case rwSIZEOF:
            printf("rwSIZEOF\n");
            break;
        case rwNEW:
            printf("rwNEW\n");
            break;
        case rwDELETE:
            printf("rwDELETE\n");
            break;
        case rwCONTINUE:
            printf("rwCONTINUE\n");
            break;
        case rwBREAK:
            printf("rwBREAK\n");
            break;
        case rwASM:
            printf("rwASM\n");
            break;
        case rwATTRIBUTE:
            printf("rwATTRIBUTE\n");
            break;
        case rwMUTABLE:
            printf("rwMUTABLE\n");
            break;
        case rwCONST_CAST:
            printf("rwCONST_CAST\n");
            break;
        case rwSTATIC_CAST:
            printf("rwSTATIC_CAST\n");
            break;
        case rwDYNAMIC_CAST:
            printf("rwDYNAMIC_CAST\n");
            break;
        case rwREINTERPRET_CAST:
            printf("rwREINTERPRET_CAST\n");
            break;
        case rwINLINE:
            printf("rwINLINE\n");
            break;
        case rwRETURN:
            printf("rwRETURN\n");
            break;
        case rwGOTO:
            printf("rwGOTO\n");
            break;
        case rwTHROW:
            printf("rwTHROW\n");
            break;
        case rwVOLATILE:
            printf("rwVOLATILE\n");
            break;
        case rwSTATIC:
            printf("rwSTATIC\n");
            break;
        case rwEXTERN:
            printf("rwEXTERN\n");
            break;
        case rwUSING:
            printf("rwUSING\n");
            break;
        case rwPUBLIC:
            printf("rwPUBLIC\n");
            break;
        case rwPROTECTED:
            printf("rwPROTECTED\n");
            break;
        case rwPRIVATE:
            printf("rwPRIVATE\n");
            break;
        case rwREGISTER:
            printf("rwREGISTER\n");
            break;
        case rwTHIS:
            printf("rwTHIS\n");
            break;
        case rwFRIEND:
            printf("rwFRIEND\n");
            break;
        case rwEXPLICIT:
            printf("rwEXPLICIT\n");
            break;
        case rwTYPEID:
            printf("rwTYPEID\n");
            break;
        case rwOPERATOR:
            printf("rwOPERATOR\n");
            break;
        case tkOP:
            printf("tkOP\n");
            break;
        case tkCC:
            printf("tkCC\n");
            break;
        case tkLEFTP:
            printf("tkLEFTP\n");
            break;
        case tkRIGHTP:
            printf("tkRIGHTP\n");
            break;
        case tkLEFTB:
            printf("tkLEFTB\n");
            break;
        case tkRIGHTB:
            printf("tkRIGHTB\n");
            break;
        case tkTERM:
            printf("tkTERM\n");
            break;
        case tkWAVE:
            printf("tkWAVE\n");
            break;
        case tkDOT:
            printf("tkDOT\n");
            break;

        case tkMACRO_DEFINE:
            printf("tkMACRO_DEFINE: `%s'\n", yytext);
            break;
        }
    }
    return 0;
}
#endif
